iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
0
Modern Web

從技術文章深入學習 JavaScript系列 第 19

Day 19 [其他03] 柯里化與反柯里化

  • 分享至 

  • xImage
  •  

文章選自

作者:dendoink

連接:https://juejin.im/post/6844903645222273037

來源:掘金

柯里化

逐步接收參數的過程:

可以理解為提前接收部分參數,延遲執行,不立即輸出結果,而是返回一個接受剩餘參數的函數。因為這樣的特性,也被稱為部分計算函數。

例子

實現add(1)(2, 3)(4)() = 10的效果

  1. 傳入參數,代碼不執行結果,而是先記憶起來
  2. 當傳入空參數及代表執行

完整代碼:

function currying(fn) {
  let allArgs = [];
  
  return function next() {
    let args = [].slice.call(arguments);

    if (args.length > 0) {
      allArgs = allArgs.concat(args);
      return next;
    } else {
      return fn.apply(null, allArgs);
    }
  }
}
let add = currying(function () {
  let sum = 0;
  for (let i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
});

透過閉包儲存資料

function currying(fn) {
  // 因為外面的add會指向next函數,且next函數裡面引用到了allArgs
  let allArgs = [];
  
  return function next() {
    // 將arguments對象轉成數組([]不代表任何意義只是為了調用slice方法)
    // 所以也可以這樣 Array.prototype.slice.call(arguments)
    let args = [].slice.call(arguments);
	
    // 當傳入參數數量大於零就推進allArgs數組裡面
    if (args.length > 0) {
      allArgs = allArgs.concat(args);
      return next;
    } else {
      return fn.apply(null, allArgs);
    }
  }
}

小結

從此例子可以得出明顯的規範

  1. 逐步接收參數,並緩存供後期計算使用(這裡使用閉包)
  2. 不立即計算,延後執行
  3. 最後將緩存的參數一併傳給原函數並執行

延伸

來實現 add(1)(2, 3)(4)('5')

透過隱式調用valueOftoString方式結束延後執行

function currying(fn) {
  let allArgs = [];

  function next() {
    let args = [].slice.call(arguments);
    allArgs = allArgs.concat(args);
    return next;
  }
  // 字符類型(一旦調用到這方法及立刻執行函數)
  next.toString = function () {
    return fn.apply(null, allArgs);
  };
  // 數值類型(一旦調用到這方法及立刻執行函數)
  next.valueOf = function () {
    return fn.apply(null, allArgs);
  }

  return next;
}


let add = currying(function () {
  let sum = 0;
  for (let i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }
  return sum;
});

console.log(add(1)(2, 3)(4)('5'));


反柯里化

是一個泛型化的過程。它使得被反柯里化的函數,可以接收更多參數。目的是創建一個更普適性的函數,可以被不同的對象使用。有鳩占鵲巢的效果。

例子

我們透過反柯里化成功讓非Toast類的obj成功調用了Toast私人方法

// 創建Toast類
function Toast(option) {
  this.prompt = '';
}
Toast.prototype = {
  constructor: Toast,
  // Toast類的私有方法
  show: function () {
    console.log(this.prompt);
  }
};

// 新對象
let obj = {
  prompt: '新對象'
};

// 反柯里化函數
function unCurrying(fn) {
  return function () {
    // 將arguments類型轉成數組
    let args = [].slice.call(arguments);
    let that = args.shift();
    return fn.apply(that, args);
  }
}

var objShow = unCurrying(Toast.prototype.show);

objShow(obj); // 輸出 '新對象'

另一種寫法

Function.prototype.unCurrying = function(){
    var self = this;
    return function(){
        return Function.prototype.call.apply(self, arguments);
    }
}

// 使用
var objShow = Toast.prototype.show.unCurrying();
objShow(obj);

解析 :

// 先來看看這個
function foo() {
  console.log(this.name);
}
let obj = {
  name: 'Mike'
}

Function.prototype.call.apply(foo, [obj]) // 'Mike'
// 1. 因為call也是函數所以當然有apply方法
// 2. 先看後面apply(foo.[obj]),可以把他想像成callFunction.apply(foo,[obj])
// 3. 我們將foo綁定給callFunction的this(call會執行他),並把[obj]當成參數綁定給他
// 4. 所以其實會跟這個類似
		foo.call(obj) // 'Mike'
// 5. 至於我們為何要做這麼複雜的事情呢?
//	  因為這裡我們只能透過this(即self)獲得函數,因此必須透因此必須透過apply或者是call執行函數

實戰

判斷變量類型(反柯里化)

目的:

透過Object.prototype.toString 判斷類型

基本寫法

var fn = function () { };
var val = 1;

if (Object.prototype.toString.call(fn) == '[object Function]') {
  console.log(`${fn} is function.`);
}

if (Object.prototype.toString.call(val) == '[object Number]') {
  console.log(`${val} is number.`);
}

反柯里化寫法:

Function.prototype.unCurrying = function () {
  var self = this;
  return function () {
    return Function.prototype.call.apply(self, arguments);
  }
}

var fn = function () { };
var val = 1;
var toString = Object.prototype.toString.unCurrying();

// 不需要再透過綁定
if (toString(fn) == '[object Function]') {
  console.log(`${fn} is function.`);
}

if (toString(val) == '[object Number]') {
  console.log(`${val} is number.`);
}

監聽事件(柯里化)

function nodeListen(node, eventName) {
  return function (fn) {
    node.addEventListener(eventName, function () {
      fn.apply(this, Array.prototype.slice.call(arguments));
    }, false);
  }
}

var bodyClickListen = nodeListen(document.body, 'click');
bodyClickListen(function () {
  console.log('first listen');
});

bodyClickListen(function () {
  console.log('second listen');
});



上一篇
Day 18 [123] [前端漫談_1] 從 for of 聊到 Generator
下一篇
Day 20 [整理02] 淺析JavaScript閉包
系列文
從技術文章深入學習 JavaScript29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言